using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using QiHe.CodeLib;
using System.Reflection;
using System.Xml.Serialization;

namespace QiHe.ReliableData
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            LoadSettings();
        }

        private void LoadSettings()
        {
            FormClosing += delegate
            {
                Settings.Default.LastFile = packageFile;
                Settings.Default.Save();
            };
        }

        GenCodeDialog genCodeDialog = new GenCodeDialog();

        Package package = new Package();
        string Title;

        private void Form1_Load(object sender, EventArgs e)
        {
            Title = this.Text;
            if (File.Exists(Settings.Default.LastFile))
            {
                LoadReliableDataPackage(Settings.Default.LastFile);
            }
        }

        string packageFile;

        private void loadLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string file = FileSelector.BrowseFile(FileType.Xml);
            if (file != null)
            {
                bool loaded = LoadReliableDataPackage(file);
                if (loaded)
                {
                    Settings.Default.LastFile = file;
                }
            }
        }

        private void reloadRToolStripMenuItem_Click(object sender, EventArgs e)
        {
            LoadReliableDataPackage(packageFile);
        }

        private bool LoadReliableDataPackage(string file)
        {
            package = XmlDeSerialization.DeSerialize(file);
            if (package == null)
            {
                package = new Package();
                MessageBox.Show("Not a valid ReliableData file.");
                return false;
            }
            packageFile = file;
            this.Text = packageFile;
            string settingsFile = AppSettings.InferSettingsFile(file);
            if (File.Exists(settingsFile))
            {
                Settings.LoadToDefault(settingsFile);
            }
            ListPackage();
            FileComboBoxTypes();
            return true;
        }

        private void ListPackage()
        {
            ClearTreeViews();
            foreach (DataType dataType in package.DataTypes)
            {
                TreeNode node = CreateTreeNode(null, dataType);
                AddNodeToTreeView(treeViewType, node, package.DataTypes);
            }
            foreach (EnumType enumType in package.EnumTypes)
            {
                TreeNode node = CreateTreeNode(null, enumType);
                AddNodeToTreeView(treeViewType, node, package.EnumTypes);
            }
            foreach (object data in package.DataObjects)
            {
                TreeNode node = CreateTreeNode(null, data);
                AddNodeToTreeView(treeViewData, node, package.DataObjects);
            }
        }

        private void FileComboBoxTypes()
        {
            toolStripComboBoxTypes.Items.Clear();
            foreach (DataType dataType in package.DataTypes)
            {
                toolStripComboBoxTypes.Items.Add(dataType);
            }
            toolStripComboBoxTypes.Sorted = true;
            if (toolStripComboBoxTypes.Items.Count > 0)
            {
                toolStripComboBoxTypes.SelectedIndex = 0;
            }
        }

        private static void AddNodeToTreeView(TreeView treeView, TreeNode node, IList container)
        {
            treeView.Nodes.Add(node);
            if (node.ContextMenu == null)
            {
                node.ContextMenu = new ContextMenu();
            }
            node.ContextMenu.MenuItems.Add(
                   new MenuItem("Delete",
                   delegate(object obj, EventArgs eArg)
                   {
                       container.Remove(node.Tag);
                       treeView.Nodes.Remove(node);
                   })
            );
        }

        private void addNewDataToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DataType dataType = toolStripComboBoxTypes.SelectedItem as DataType;
            if (dataType == null)
            {
                MessageBox.Show("Select a type first."); return;
            }
            tabControl1.SelectedIndex = 1;
            Type type = Package.Types[dataType.Name];
            TreeNode node = CreateTreeNode(null, type);
            AddNodeToTreeView(treeViewData, node, package.DataObjects);
            treeViewData.SelectedNode = node;
        }

        private TreeNode CreateTreeNode(string name, Type type)
        {
            TreeNode node = new TreeNode(name == null ? type.Name : name);
            object data = Activator.CreateInstance(type);
            node.Tag = data;
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo pi in properties)
            {
                Type fieldType = pi.PropertyType;
                if (TypeHelper.IsGenericList(fieldType))
                {
                    object fieldData = Activator.CreateInstance(fieldType);
                    pi.SetValue(data, fieldData, null);
                    Type itemType = fieldType.GetGenericArguments()[0];
                    if (!TypeHelper.IsAtomType(itemType))
                    {
                        TreeNode subnode = node;
                        if (!pi.IsDefined(typeof(XmlElementAttribute), false))
                        {
                            subnode = new TreeNode(pi.Name);
                            subnode.Tag = fieldData;
                            node.Nodes.Add(subnode);
                        }
                        CreateNodeContextMenuForListType(itemType, fieldType, fieldData, subnode);
                        MethodInfo removeMethod = fieldType.GetMethod("Remove", new Type[] { itemType });
                        foreach (object item in (IEnumerable)fieldData)
                        {
                            TreeNode itemNode = CreateTreeNode(null, item);
                            subnode.Nodes.Add(itemNode);
                            AddDeleteMenuToListItemNode(itemNode, fieldData, removeMethod);
                        }
                    }
                }
                else if (!TypeHelper.IsAtomType(fieldType) && fieldType != type)
                {
                    TreeNode subnode = CreateTreeNode(pi.Name, fieldType);
                    subnode.BackColor = Color.LightGray;
                    node.Nodes.Add(subnode);
                    pi.SetValue(data, subnode.Tag, null);
                }
            }
            return node;
        }

        private void CreateNodeContextMenuForListType(Type itemType, Type listType, object fieldData, TreeNode subnode)
        {
            string typeName = itemType.Name;

            MethodInfo addMethod = listType.GetMethod("Add", new Type[] { itemType });
            MethodInfo removeMethod = listType.GetMethod("Remove", new Type[] { itemType });
            MethodInfo insertMethod = listType.GetMethod("Insert", new Type[] { typeof(int), itemType });

            if (subnode.ContextMenu == null)
            {
                subnode.ContextMenu = new ContextMenu();
            }
            subnode.ContextMenu.MenuItems.Add(
                        new MenuItem("Add " + typeName,
                        delegate(object sender, EventArgs e)
                        {
                            TreeNode itemNode = CreateTreeNode(null, itemType);
                            subnode.Nodes.Add(itemNode);
                            addMethod.Invoke(fieldData, new object[] { itemNode.Tag });
                            subnode.Expand();
                            AddDeleteMenuToListItemNode(itemNode, fieldData, removeMethod);
                            itemNode.TreeView.SelectedNode = itemNode;
                        }
                        ));
        }

        private static void AddDeleteMenuToListItemNode(TreeNode itemNode, object fieldData, MethodInfo removeMethod)
        {
            if (itemNode.ContextMenu == null)
            {
                itemNode.ContextMenu = new ContextMenu();
            }
            itemNode.ContextMenu.MenuItems.Add(
                       new MenuItem("Delete",
                       delegate(object obj, EventArgs eArg)
                       {
                           removeMethod.Invoke(fieldData, new object[] { itemNode.Tag });
                           itemNode.Parent.Nodes.Remove(itemNode);
                       }
                       ));
            //AddMoveMenuItems(itemNode);
        }

        private static void AddMoveMenuItems(TreeNode itemNode)
        {
            itemNode.ContextMenu.MenuItems.Add(
                       new MenuItem("Move Up",
                       delegate(object obj, EventArgs eArg)
                       {
                           int index = itemNode.Index;
                           TreeNode parent = itemNode.Parent;
                           parent.Nodes.Remove(itemNode);
                           parent.Nodes.Insert(index - 1, itemNode);
                           parent.TreeView.SelectedNode = itemNode;
                       }
                       ));
            itemNode.ContextMenu.MenuItems.Add(
                       new MenuItem("Move Down",
                       delegate(object obj, EventArgs eArg)
                       {
                           int index = itemNode.Index;
                           TreeNode parent = itemNode.Parent;
                           parent.Nodes.Remove(itemNode);
                           parent.Nodes.Insert(index + 1, itemNode);
                           parent.TreeView.SelectedNode = itemNode;
                       }
                       ));
        }

        private TreeNode CreateTreeNode(string name, object data)
        {
            TreeNode node = new TreeNode(name == null ? TypeHelper.TryGetName(data) : name);
            node.Tag = data;
            Type type = data.GetType();
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo pi in properties)
            {
                Type fieldType = pi.PropertyType;
                object fieldData = pi.GetValue(data, null);
                if (TypeHelper.IsGenericList(fieldType))
                {
                    if (fieldData == null)
                    {
                        fieldData = Activator.CreateInstance(fieldType);
                        pi.SetValue(data, fieldData, null);
                    }
                    Type itemType = fieldType.GetGenericArguments()[0];
                    if (!TypeHelper.IsAtomType(itemType))
                    {
                        TreeNode subnode = node;
                        if (!pi.IsDefined(typeof(XmlElementAttribute), false))
                        {
                            subnode = new TreeNode(pi.Name);
                            subnode.Tag = fieldData;
                            node.Nodes.Add(subnode);
                        }
                        CreateNodeContextMenuForListType(itemType, fieldType, fieldData, subnode);
                        MethodInfo removeMethod = fieldType.GetMethod("Remove", new Type[] { itemType });
                        foreach (object item in (IEnumerable)fieldData)
                        {
                            TreeNode itemNode = CreateTreeNode(null, item);
                            subnode.Nodes.Add(itemNode);
                            AddDeleteMenuToListItemNode(itemNode, fieldData, removeMethod);
                        }
                    }
                }
                else if (!TypeHelper.IsAtomType(fieldType) && fieldType != type)
                {
                    if (fieldData == null)
                    {
                        fieldData = Activator.CreateInstance(fieldType);
                        pi.SetValue(data, fieldData, null);
                    }
                    TreeNode subnode = CreateTreeNode(pi.Name, fieldData);
                    subnode.BackColor = Color.LightGray;
                    node.Nodes.Add(subnode);
                }
            }
            return node;
        }

        private void saveSToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!File.Exists(packageFile))
            {
                packageFile = FileSelector.BrowseFileForSave(FileType.Xml);
            }
            SavePackage();
        }

        private void SavePackage()
        {
            if (packageFile != null)
            {
                RefreshPackage();
                XmlSerialization.Serialize(packageFile, package);
                string settingsFile = AppSettings.InferSettingsFile(packageFile);
                Settings.Default.Save(settingsFile);
                this.Text = packageFile;
            }
        }

        private void RefreshPackage()
        {
            package.DataTypes.Clear();
            package.EnumTypes.Clear();
            foreach (TreeNode node in treeViewType.Nodes)
            {
                if (node.Tag is DataType)
                {
                    package.DataTypes.Add((DataType)node.Tag);
                }
                else if (node.Tag is EnumType)
                {
                    package.EnumTypes.Add((EnumType)node.Tag);
                }
            }
            package.DataObjects.Clear();
            foreach (TreeNode node in treeViewData.Nodes)
            {
                package.DataObjects.Add(node.Tag);
            }
        }

        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            propertyGrid.SelectedObject = e.Node.Tag;
            string typeName = e.Node.Tag.GetType().Name;
            string objectName = TypeHelper.TryGetName(e.Node.Tag, null);
            if (objectName == null && e.Node.Parent != null)
            {
                objectName = typeName + " " + e.Node.Parent.Text + "." + e.Node.Text;
            }
            else
            {
                objectName = typeName + " " + objectName;
            }
            toolStripStatusLabelDataName.Text = objectName;
        }

        private void propertyGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            if (e.ChangedItem.Label == "Name")
            {
                TreeNode node = GetSelectedTreeNode();
                node.Text = e.ChangedItem.Value.ToString();
            }
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string file = FileSelector.BrowseFileForSave(FileType.Xml);
            if (file != null)
            {
                packageFile = file;
                SavePackage();
            }
        }

        private void newNToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (treeViewType.Nodes.Count > 0 || treeViewData.Nodes.Count > 0)
            {
                if (MessageBox.Show("Sure to clear existed types and data?",
                    "Confirm",
                    MessageBoxButtons.OKCancel
                    ) == DialogResult.Cancel)
                {
                    return;
                }
            }
            packageFile = null;
            this.Text = Title;
            ClearTreeViews();
            propertyGrid.SelectedObject = null;
        }

        private void ClearTreeViews()
        {
            treeViewType.Nodes.Clear();
            treeViewData.Nodes.Clear();
            toolStripComboBoxTypes.Items.Clear();
        }

        private void generateCodeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            genCodeDialog.Package = package;
            genCodeDialog.ShowDialog();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show(
                Title + "\r\n" +
                "2006-06-08 version 1.0\r\n" +
                "2006-06-13 version 1.1\r\n" +
                "Liu Junfeng" + "\r\n" +
                "rufi@163.com" + "\r\n",
                "About"
                );
        }

        private void exitXToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void packageToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ListPackage();
        }

        private void addDataTypeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TreeNode node = CreateTreeNode(null, new DataType());
            AddNodeToTreeView(treeViewType, node, package.DataTypes);
            treeViewType.SelectedNode = node;
        }

        private void addEnumTypeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TreeNode node = CreateTreeNode(null, new EnumType());
            AddNodeToTreeView(treeViewType, node, package.EnumTypes);
            treeViewType.SelectedNode = node;
        }

        private void emitTypesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                RefreshPackage();
                package.BuildTypes();
                FileComboBoxTypes();
            }
            catch (Exception error)
            {
                MessageBox.Show(error.Message);
            }
        }

        private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
        {
            TreeNode node = GetSelectedTreeNode();
            if (node != null)
            {
                treeView_AfterSelect(null, new TreeViewEventArgs(node));
            }
        }

        private TreeNode GetSelectedTreeNode()
        {
            TreeView treeView = tabControl1.SelectedIndex == 0 ? treeViewType : treeViewData;
            return treeView.SelectedNode;
        }

        public List<string> GetValueSet(string valuefield)
        {
            List<string> valueset = new List<string>();
            if (valuefield.StartsWith("{") && valuefield.EndsWith("}"))
            {
                string[] items = valuefield
                    .Substring(1, valuefield.Length - 2)
                    .Split(',');
                foreach (string item in items)
                {
                    valueset.Add(item.Trim());
                }
            }
            else
            {
                string[] items = valuefield.Split('.');
                if (items.Length == 2)
                {
                    string typeName = items[0];
                    PropertyInfo pi = Package.Types[typeName].GetProperty(items[1]);
                    foreach (TreeNode node in TreeViewHelper.Tranverse(treeViewData))
                    {
                        if (node.Tag.GetType().Name == typeName)
                        {
                            object value = pi.GetValue(node.Tag, null);
                            if (value != null)
                            {
                                string item = value.ToString();
                                if (!valueset.Contains(item))
                                {
                                    valueset.Add(item);
                                }
                            }
                        }
                    }
                }
                valueset.Sort();
            }
            return valueset;
        }

        public List<string> GetValueSet(string dataTypeName, string propertyName)
        {
            DataType dataType = package.FindDataType(dataTypeName);
            TypeField field = dataType.GetTypeField(propertyName);
            string valuefield = field.ValueSet;
            return GetValueSet(valuefield);
        }

        public bool IsFixedValueSet(string dataTypeName, string propertyName)
        {
            DataType dataType = package.FindDataType(dataTypeName);
            TypeField field = dataType.GetTypeField(propertyName);
            string valuefield = field.ValueSet;
            return valuefield.StartsWith("{");
        }
    }
}